
/*
 * Copyright (c) 2023 Infineon Technologies AG
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE
 */
/*
 * This file was copied from https://github.com/devttys0/libmpsse.git (sha1: a2eafa2)
 * and modified accordingly.
 *
 * Copyright (c) 2015, Craig Heffner
 * All rights reserved.
 * SPDX short identifier: BSD-2-Clause
 */

#include <stdint.h>      // for uint8_t, uint32_t, int32_t, uint16_t
#include <stdlib.h>      // for NULL, free, malloc
#include <string.h>      // for memset
#include <sys/select.h>  // for select, timeval

#include "mpsse.h"
#include "support.h"     // for is_valid_context, raw_write, set_bits_low

/* List of known FT2232-based devices */
struct vid_pid supported_devices[] = {
    { 0x0403, 0x6014, "FT232H Future Technology Devices International, Ltd" },
    { 0x0403, 0x6010, "FT2232 Future Technology Devices International, Ltd" },
    { 0x0403, 0x6011, "FT4232 Future Technology Devices International, Ltd" },

    /* These devices are based on FT2232 chips, but have not been tested. */
    { 0x0403, 0x8878, "Bus Blaster v2 (channel A)" },
    { 0x0403, 0x8879, "Bus Blaster v2 (channel B)" },
    { 0x0403, 0xBDC8, "Turtelizer JTAG/RS232 Adapter A" },
    { 0x0403, 0xCFF8, "Amontec JTAGkey" },
    { 0x0403, 0x8A98, "TIAO Multi Protocol Adapter"},
    { 0x15BA, 0x0003, "Olimex Ltd. OpenOCD JTAG" },
    { 0x15BA, 0x0004, "Olimex Ltd. OpenOCD JTAG TINY" },

    { 0, 0, NULL }
};

/*
 * Opens and initializes the first FTDI device found.
 *
 * @mode      - Mode to open the device in. One of enum modes.
 * @freq      - Clock frequency to use for the specified mode.
 * @endianess - Specifies how data is clocked in/out (MSB, LSB).
 *
 * Returns a pointer to an MPSSE context structure.
 * On success, mpsse->open will be set to 1.
 * On failure, mpsse->open will be set to 0.
 */
struct mpsse_context *MPSSE (enum modes mode, int freq, int endianess)
{
    int i = 0;
    struct mpsse_context *mpsse = NULL;

    for (i=0; supported_devices[i].vid != 0; i++)
    {
        if ((mpsse = Open (supported_devices[i].vid, supported_devices[i].pid, mode, freq, endianess, IFACE_A, NULL, NULL)) != NULL)
        {
            if (mpsse->open)
            {
                mpsse->description = supported_devices[i].description;
                break;
            }
            /* If there is another device still left to try, free the context pointer and try again */
            else if (supported_devices[i+1].vid != 0)
            {
                Close (mpsse);
                mpsse = NULL;
            }
        }
    }

    return mpsse;
}

/*
 * Open device by VID/PID
 *
 * @vid         - Device vendor ID.
 * @pid         - Device product ID.
 * @mode        - MPSSE mode, one of enum modes.
 * @freq        - Clock frequency to use for the specified mode.
 * @endianess   - Specifies how data is clocked in/out (MSB, LSB).
 * @interface   - FTDI interface to use (IFACE_A - IFACE_D).
 * @description - Device product description (set to NULL if not needed).
 * @serial      - Device serial number (set to NULL if not needed).
 *
 * Returns a pointer to an MPSSE context structure.
 * On success, mpsse->open will be set to 1.
 * On failure, mpsse->open will be set to 0.
 */
struct mpsse_context *Open (int vid, int pid, enum modes mode, int freq, int endianess, int interface, const char *description, const char *serial)
{
    return OpenIndex (vid, pid, mode, freq, endianess, interface, description, serial, 0);
}

/*
 * Open device by VID/PID/index
 *
 * @vid         - Device vendor ID.
 * @pid         - Device product ID.
 * @mode        - MPSSE mode, one of enum modes.
 * @freq        - Clock frequency to use for the specified mode.
 * @endianess   - Specifies how data is clocked in/out (MSB, LSB).
 * @interface   - FTDI interface to use (IFACE_A - IFACE_D).
 * @description - Device product description (set to NULL if not needed).
 * @serial      - Device serial number (set to NULL if not needed).
 * @index       - Device index (set to 0 if not needed).
 *
 * Returns a pointer to an MPSSE context structure.
 * On success, mpsse->open will be set to 1.
 * On failure, mpsse->open will be set to 0.
 */
struct mpsse_context *OpenIndex (int vid, int pid, enum modes mode, int freq, int endianess, int interface, const char *description, const char *serial, int index)
{
    int status = 0;
    struct mpsse_context *mpsse = NULL;

    mpsse = malloc (sizeof(struct mpsse_context));
    if (mpsse)
    {
        memset (mpsse, 0, sizeof (struct mpsse_context));

        /* Legacy; flushing is no longer needed, so disable it by default. */
        FlushAfterRead (mpsse, 0);

        /* ftdilib initialization */
        if (ftdi_init (&mpsse->ftdi) == 0)
        {
            /* Set the FTDI interface  */
            ftdi_set_interface (&mpsse->ftdi, interface);

            /* Open the specified device */
            if (ftdi_usb_open_desc_index (&mpsse->ftdi, vid, pid, description, serial, index) == 0)
            {
                mpsse->mode = mode;
                mpsse->vid = vid;
                mpsse->pid = pid;
                mpsse->status = STOPPED;
                mpsse->endianess = endianess;

                /* Set the appropriate transfer size for the requested protocol */
                if (mpsse->mode == I2C)
                {
                    mpsse->xsize = I2C_TRANSFER_SIZE;
                }
                else
                {
                    mpsse->xsize = SPI_RW_SIZE;
                }

                status |= ftdi_usb_reset (&mpsse->ftdi);
                status |= ftdi_set_latency_timer (&mpsse->ftdi, LATENCY_MS);
                status |= ftdi_write_data_set_chunksize (&mpsse->ftdi, CHUNK_SIZE);
                status |= ftdi_read_data_set_chunksize (&mpsse->ftdi, CHUNK_SIZE);
                status |= ftdi_set_bitmode (&mpsse->ftdi, 0, BITMODE_RESET);

                if (status == 0)
                {
                    /* Set the read and write timeout periods */
                    set_timeouts (mpsse, USB_TIMEOUT);

                    if (mpsse->mode != BITBANG)
                    {
                        ftdi_set_bitmode (&mpsse->ftdi, 0, BITMODE_MPSSE);

                        if (SetClock (mpsse, freq) == MPSSE_OK)
                        {
                            if (SetMode (mpsse, endianess) == MPSSE_OK)
                            {
                                int32_t ms = SETUP_DELAY/1000;
                                struct timeval tv = {ms/1000, (ms%1000)*1000};

                                mpsse->open = 1;

                                /* Give the chip a few mS to initialize */
                                select (0, NULL, NULL, NULL, &tv);

                                /*
                                 * Not all FTDI chips support all the commands that SetMode may have sent.
                                 * This clears out any errors from unsupported commands that might have been sent during set up.
                                 */
                                ftdi_usb_purge_buffers (&mpsse->ftdi);
                            }
                        }
                    }
                    else
                    {
                        /* Skip the setup functions if we're just operating in BITBANG mode */
                        if (ftdi_set_bitmode (&mpsse->ftdi, 0xFF, BITMODE_BITBANG) == 0)
                        {
                            mpsse->open = 1;
                        }
                    }
                }
            }
        }
    }

    return mpsse;
}

/*
 * Closes the device, deinitializes libftdi, and frees the MPSSE context pointer.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns void.
 */
void Close (struct mpsse_context *mpsse)
{
    if (mpsse)
    {
        if (mpsse->open)
        {
            ftdi_set_bitmode (&mpsse->ftdi, 0, BITMODE_RESET);
            ftdi_usb_close (&mpsse->ftdi);
            ftdi_deinit (&mpsse->ftdi);
        }

        free (mpsse);
        mpsse = NULL;
    }

    return;
}

/* Enables bit-wise data transfers.
 * Must be called after MPSSE() / Open() / OpenIndex().
 *
 * Returns void.
 */
void EnableBitmode (struct mpsse_context *mpsse, int tf)
{
    if (is_valid_context (mpsse))
    {
        if (tf)
        {
            mpsse->tx |= MPSSE_BITMODE;
            mpsse->rx |= MPSSE_BITMODE;
            mpsse->txrx |= MPSSE_BITMODE;
        }
        else
        {
            mpsse->tx &= ~MPSSE_BITMODE;
            mpsse->rx &= ~MPSSE_BITMODE;
            mpsse->txrx &= ~MPSSE_BITMODE;
        }
    }
}

/*
 * Sets the appropriate transmit and receive commands based on the requested mode and byte order.
 *
 * @mpsse     - MPSSE context pointer.
 * @endianess - MPSSE_MSB or MPSSE_LSB.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int SetMode (struct mpsse_context *mpsse, int endianess)
{
    int retval = MPSSE_OK, i = 0, setup_commands_size = 0;
    unsigned char buf[CMD_SIZE] = { 0 };
    unsigned char setup_commands[CMD_SIZE*MAX_SETUP_COMMANDS] = { 0 };

    /* Do not call is_valid_context() here, as the FTDI chip may not be completely configured when SetMode is called */
    if (mpsse)
    {
        /* Read and write commands need to include endianess */
        mpsse->tx   = MPSSE_DO_WRITE | endianess;
        mpsse->rx   = MPSSE_DO_READ  | endianess;
        mpsse->txrx = MPSSE_DO_WRITE | MPSSE_DO_READ | endianess;

        /* Clock, data out, chip select pins are outputs; all others are inputs. */
        mpsse->tris = DEFAULT_TRIS;

        /* Clock and chip select pins idle high; all others are low */
        mpsse->pidle = mpsse->pstart = mpsse->pstop = DEFAULT_PORT;

        /* During reads and writes the chip select pin is brought low */
        mpsse->pstart &= ~TMS;

        /* Disable FTDI internal loopback */
        SetLoopback (mpsse, 0);

        /* Send ACKs by default */
        SetAck (mpsse, ACK);

        /* Ensure adaptive clock is disabled */
        setup_commands[setup_commands_size++] = DISABLE_ADAPTIVE_CLOCK;

        switch (mpsse->mode)
        {
            case SPI0:
                /* SPI mode 0 clock idles low */
                mpsse->pidle &= ~SK;
                mpsse->pstart &= ~SK;
                mpsse->pstop &= ~SK;
                /* SPI mode 0 propogates data on the falling edge and read data on the rising edge of the clock */
                mpsse->tx |= MPSSE_WRITE_NEG;
                mpsse->rx &= ~MPSSE_READ_NEG;
                mpsse->txrx |= MPSSE_WRITE_NEG;
                mpsse->txrx &= ~MPSSE_READ_NEG;
                break;
            case SPI3:
                /* SPI mode 3 clock idles high */
                mpsse->pidle |= SK;
                mpsse->pstart |= SK;
                /* Keep the clock low while the CS pin is brought high to ensure we don't accidentally clock out an extra bit */
                mpsse->pstop &= ~SK;
                /* SPI mode 3 propogates data on the falling edge and read data on the rising edge of the clock */
                mpsse->tx |= MPSSE_WRITE_NEG;
                mpsse->rx &= ~MPSSE_READ_NEG;
                mpsse->txrx |= MPSSE_WRITE_NEG;
                mpsse->txrx &= ~MPSSE_READ_NEG;
                break;
            case SPI1:
                /* SPI mode 1 clock idles low */
                mpsse->pidle &= ~SK;
                /* Since this mode idles low, the start condition should ensure that the clock is low */
                mpsse->pstart &= ~SK;
                /* Even though we idle low in this mode, we need to keep the clock line high when we set the CS pin high to prevent
                 * an unintended clock cycle from being sent by the FT2232. This way, the clock goes high, but does not go low until
                 * after the CS pin goes high.
                 */
                mpsse->pstop |= SK;
                /* Data read on falling clock edge */
                mpsse->rx |= MPSSE_READ_NEG;
                mpsse->tx &= ~MPSSE_WRITE_NEG;
                mpsse->txrx |= MPSSE_READ_NEG;
                mpsse->txrx &= ~MPSSE_WRITE_NEG;
                break;
            case SPI2:
                /* SPI 2 clock idles high */
                mpsse->pidle |= SK;
                mpsse->pstart |= SK;
                mpsse->pstop |= SK;
                /* Data read on falling clock edge */
                mpsse->rx |= MPSSE_READ_NEG;
                mpsse->tx &= ~MPSSE_WRITE_NEG;
                mpsse->txrx |= MPSSE_READ_NEG;
                mpsse->txrx &= ~MPSSE_WRITE_NEG;
                break;
            case I2C:
                /* I2C propogates data on the falling clock edge and reads data on the falling (or rising) clock edge */
                mpsse->tx |= MPSSE_WRITE_NEG;
                mpsse->rx &= ~MPSSE_READ_NEG;
                /* In I2C, both the clock and the data lines idle high */
                mpsse->pidle |= DO | DI;
                /* I2C start bit == data line goes from high to low while clock line is high */
                mpsse->pstart &= ~DO & ~DI;
                /* I2C stop bit == data line goes from low to high while clock line is high - set data line low here, so the transition to the idle state triggers the stop condition. */
                mpsse->pstop &= ~DO & ~DI;
                /* Enable three phase clock to ensure that I2C data is available on both the rising and falling clock edges */
                setup_commands[setup_commands_size++] = ENABLE_3_PHASE_CLOCK;
                break;
            case GPIO:
                break;
            default:
                retval = MPSSE_FAIL;
        }

        /* Send any setup commands to the chip */
        if (retval == MPSSE_OK)
        {
            retval = raw_write (mpsse, setup_commands, setup_commands_size);
        }

        if (retval == MPSSE_OK)
        {
            /* Set the idle pin states */
            set_bits_low (mpsse, mpsse->pidle);

            /* All GPIO pins are outputs, set low */
            mpsse->trish = 0xFF;
            mpsse->gpioh = 0x00;

            buf[i++] = SET_BITS_HIGH;
            buf[i++] = mpsse->gpioh;
            buf[i++] = mpsse->trish;

            retval = raw_write (mpsse, buf, i);
        }
    }
    else
    {
        retval = MPSSE_FAIL;
    }

    return retval;
}

/*
 * Sets the appropriate divisor for the desired clock frequency.
 *
 * @mpsse - MPSSE context pointer.
 * @freq  - Desired clock frequency in hertz.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int SetClock (struct mpsse_context *mpsse, uint32_t freq)
{
    int retval = MPSSE_FAIL;
    uint32_t system_clock = 0;
    uint16_t divisor = 0;
    unsigned char buf[CMD_SIZE] = { 0 };

    /* Do not call is_valid_context() here, as the FTDI chip may not be completely configured when SetClock is called */
    if (mpsse)
    {
        if (freq > SIX_MHZ)
        {
            buf[0] = TCK_X5;
            system_clock = SIXTY_MHZ;
        }
        else
        {
            buf[0] = TCK_D5;
            system_clock = TWELVE_MHZ;
        }

        if (raw_write (mpsse, buf, 1) == MPSSE_OK)
        {
            if (freq <= 0)
            {
                divisor = 0xFFFF;
            }
            else
            {
                divisor = freq2div (system_clock, freq);
            }

            buf[0] = TCK_DIVISOR;
            buf[1] = (divisor & 0xFF);
            buf[2] = ((divisor >> 8) & 0xFF);

            if (raw_write (mpsse, buf, 3) == MPSSE_OK)
            {
                mpsse->clock = div2freq (system_clock, divisor);
                retval = MPSSE_OK;
            }
        }
    }

    return retval;
}

/*
 * Retrieves the last error string from libftdi.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns a pointer to the last error string.
 */
const char *ErrorString (struct mpsse_context *mpsse)
{
    if (mpsse != NULL)
    {
        return ftdi_get_error_string (&mpsse->ftdi);
    }

    return NULL_CONTEXT_ERROR_MSG;
}

/*
 * Gets the currently configured clock rate.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns the existing clock rate in hertz.
 */
uint32_t GetClock (struct mpsse_context *mpsse)
{
    if (! is_valid_context (mpsse))
    {
        return 0;
    }

    return mpsse->clock;
}

/*
 * Returns the vendor ID of the FTDI chip.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns the integer value of the vendor ID.
 */
int GetVid (struct mpsse_context *mpsse)
{
    int vid = 0;

    if (is_valid_context (mpsse))
    {
        vid = mpsse->vid;
    }

    return vid;
}

/*
 * Returns the product ID of the FTDI chip.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns the integer value of the product ID.
 */
int GetPid (struct mpsse_context *mpsse)
{
    int pid = 0;

    if (is_valid_context (mpsse))
    {
        pid = mpsse->pid;
    }

    return pid;
}

/*
 * Returns the description of the FTDI chip, if any.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns the description of the FTDI chip.
 */
const char *GetDescription (struct mpsse_context *mpsse)
{
    char *description = NULL;

    if (is_valid_context (mpsse))
    {
        description = mpsse->description;
    }

    return description;
}

/*
 * Enable / disable internal loopback.
 *
 * @mpsse  - MPSSE context pointer.
 * @enable - Zero to disable loopback, 1 to enable loopback.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int SetLoopback (struct mpsse_context *mpsse, int enable)
{
    unsigned char buf[1] = { 0 };
    int retval = MPSSE_FAIL;

    if (is_valid_context (mpsse))
    {
        if (enable)
        {
            buf[0] = LOOPBACK_START;
        }
        else
        {
            buf[0] = LOOPBACK_END;
        }

        retval = raw_write (mpsse, buf, 1);
    }

    return retval;
}

/*
 * Sets the idle state of the chip select pin. CS idles high by default.
 *
 * @mpsse - MPSSE context pointer.
 * @idle  - Set to 1 to idle high, 0 to idle low.
 *
 * Returns void.
 */
void SetCSIdle (struct mpsse_context *mpsse, int idle)
{
    if (is_valid_context (mpsse))
    {
        if (idle > 0)
        {
            /* Chip select idles high, active low */
            mpsse->pidle |= TMS;
            mpsse->pstop |= TMS;
            mpsse->pstart &= ~TMS;
        }
        else
        {
            /* Chip select idles low, active high */
            mpsse->pidle &= ~TMS;
            mpsse->pstop &= ~TMS;
            mpsse->pstart |= TMS;
        }
    }

    return;
}

/*
 * Enables or disables flushing of the FTDI chip's RX buffers after each read operation.
 * Flushing is disable by default.
 *
 * @mpsse - MPSSE context pointer.
 * @tf    - Set to 1 to enable flushing, or 0 to disable flushing.
 *
 * Returns void.
 */
void FlushAfterRead (struct mpsse_context *mpsse, int tf)
{
    mpsse->flush_after_read = tf;
    return;
}

/*
 * Send data start condition.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int Start (struct mpsse_context *mpsse)
{
    int status = MPSSE_OK;

    if (is_valid_context (mpsse))
    {

        if (mpsse->mode == I2C && mpsse->status == STARTED)
        {
            /* Set the default pin states while the clock is low since this is an I2C repeated start condition */
            status |= set_bits_low (mpsse, (mpsse->pidle & ~SK));

            /* Make sure the pins are in their default idle state */
            status |= set_bits_low (mpsse, mpsse->pidle);
        }

        /* Set the start condition */
        status |= set_bits_low (mpsse, mpsse->pstart);

        /*
         * Hackish work around to properly support SPI mode 3.
         * SPI3 clock idles high, but needs to be set low before sending out
         * data to prevent unintenteded clock glitches from the FT2232.
         */
        if (mpsse->mode == SPI3)
        {
            status |= set_bits_low (mpsse, (mpsse->pstart & ~SK));
        }
        /*
         * Hackish work around to properly support SPI mode 1.
         * SPI1 clock idles low, but needs to be set high before sending out
         * data to preven unintended clock glitches from the FT2232.
         */
        else if (mpsse->mode == SPI1)
        {
            status |= set_bits_low (mpsse, (mpsse->pstart | SK));
        }

        mpsse->status = STARTED;
    }
    else
    {
        status = MPSSE_FAIL;
        mpsse->status = STOPPED;
    }

    return status;
}

/*
 * Performs a bit-wise write of up to 8 bits at a time.
 *
 * @mpsse - MPSSE context pointer.
 * @bits  - A byte containing the desired bits to write.
 * @size  - The number of bits from the 'bits' byte to write.
 *
 * Returns MPSSE_OK on success, MPSSE_FAIL on failure.
 */
int WriteBits (struct mpsse_context *mpsse, char bits, int size)
{
    char data[8] = { 0 };
    int i = 0, retval = MPSSE_OK;

    if (size > (int)sizeof(data))
    {
        size = sizeof(data);
    }

    /* Convert each bit in bits to an array of bytes */
    for (i=0; i<size; i++)
    {
        if (bits & (1 << i))
        {
            /* Be sure to honor endianess */
            if (mpsse->endianess == LSB)
            {
                data[i] = '\xFF';
            }
            else
            {
                data[size-i-1] = '\xFF';
            }
        }
    }

    /* Enable bit mode before writing, then disable it afterwards. */
    EnableBitmode (mpsse, 1);
    retval = Write (mpsse, data, size);
    EnableBitmode (mpsse, 0);

    return retval;
}

/*
 * Send data out via the selected serial protocol.
 *
 * @mpsse - MPSSE context pointer.
 * @data  - Buffer of data to send.
 * @size  - Size of data.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int Write (struct mpsse_context *mpsse, char *data, int size)
{
    unsigned char *buf = NULL;
    int retval = MPSSE_FAIL, buf_size = 0, txsize = 0, n = 0;

    if (is_valid_context (mpsse))
    {
        if (mpsse->mode)
        {
            while(n < size)
            {
                txsize = size - n;
                if (txsize > mpsse->xsize)
                {
                    txsize = mpsse->xsize;
                }

                /*
                 * For I2C we need to send each byte individually so that we can
                 * read back each individual ACK bit, so set the transmit size to 1.
                 */
                if (mpsse->mode == I2C)
                {
                    txsize = 1;
                }

                buf = build_block_buffer (mpsse, mpsse->tx, (unsigned char *) (data + n), txsize, &buf_size);
                if (buf)
                {
                    retval = raw_write (mpsse, buf, buf_size);
                    n += txsize;
                    free(buf);

                    if (retval == MPSSE_FAIL)
                    {
                        break;
                    }

                    /* Read in the ACK bit and store it in mpsse->rack */
                    if (mpsse->mode == I2C)
                    {
                        raw_read (mpsse, (unsigned char *) &mpsse->rack, 1);
                    }
                }
                else
                {
                    break;
                }
            }
        }

        if (retval == MPSSE_OK && n == size)
        {
            retval = MPSSE_OK;
        }
    }

    return retval;
}

/* Performs a read. For internal use only; see Read() and ReadBits(). */
char *InternalRead (struct mpsse_context *mpsse, int size)
{
    unsigned char *data = NULL, *buf = NULL;
    unsigned char sbuf[SPI_RW_SIZE] = { 0 };
    int n = 0, rxsize = 0, data_size = 0, retval = 0;

    if (is_valid_context (mpsse))
    {
        if (mpsse->mode)
        {
            buf = malloc (size);
            if (buf)
            {
                memset (buf, 0, size);

                while(n < size)
                {
                    rxsize = size - n;
                    if (rxsize > mpsse->xsize)
                    {
                        rxsize = mpsse->xsize;
                    }

                    data = build_block_buffer (mpsse, mpsse->rx, sbuf, rxsize, &data_size);
                    if (data)
                    {
                        retval = raw_write (mpsse, data, data_size);
                        free (data);

                        if (retval == MPSSE_OK)
                        {
                            n += raw_read (mpsse, buf+n, rxsize);
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }

    return (char *) buf;
}

/*
 * Reads data over the selected serial protocol.
 *
 * @mpsse - MPSSE context pointer.
 * @size  - Number of bytes to read.
 *
 * Returns a pointer to the read data on success.
 * Returns NULL on failure.
 */
#ifdef SWIGPYTHON
swig_string_data Read (struct mpsse_context *mpsse, int size)
#else
char *Read (struct mpsse_context *mpsse, int size)
#endif
{
    char *buf = NULL;

    buf = InternalRead (mpsse, size);

#ifdef SWIGPYTHON
    swig_string_data sdata = { 0 };
    sdata.size = size;
    sdata.data = buf;
    return sdata;
#else
    return buf;
#endif
}

/*
 * Performs a bit-wise read of up to 8 bits.
 *
 * @mpsse - MPSSE context pointer.
 * @size  - Number of bits to read.
 *
 * Returns an 8-bit byte containing the read bits.
 */
char ReadBits (struct mpsse_context *mpsse, int size)
{
    char bits = 0;
    char *rdata = NULL;

    if (size > 8)
    {
        size = 8;
    }

    EnableBitmode (mpsse, 1);
    rdata = InternalRead (mpsse, size);
    EnableBitmode (mpsse, 0);

    if (rdata)
    {
        /* The last byte in rdata will have all the read bits set or unset as needed. */
        bits = rdata[size-1];

        if (mpsse->endianess == MSB)
        {
            /*
             * In MSB mode, bits are sifted in from the left. If less than 8 bits were
             * read, we need to shift them left accordingly.
             */
            bits = (char) ((bits << (8-size)) & 0xff);
        }
        else if (mpsse->endianess == LSB)
        {
            /*
             * In LSB mode, bits are shifted in from the right. If less than 8 bits were
             * read, we need to shift them right accordingly.
             */
            bits = (char) ((bits >> (8-size)) & 0xff);
        }

        free (rdata);
    }

    return bits;
}

/*
 * Reads and writes data over the selected serial protocol (SPI only).
 *
 * @mpsse - MPSSE context pointer.
 * @data  - Buffer containing bytes to write.
 * @size  - Number of bytes to transfer.
 *
 * Returns a pointer to the read data on success.
 * Returns NULL on failure.
 */
#ifdef SWIGPYTHON
swig_string_data Transfer (struct mpsse_context *mpsse, char *data, int size)
#else
char *Transfer (struct mpsse_context *mpsse, char *data, int size)
#endif
{
    unsigned char *txdata = NULL, *buf = NULL;
    int n = 0, data_size = 0, rxsize = 0, retval = 0;

    if (is_valid_context (mpsse))
    {
        /* Make sure we're configured for one of the SPI modes */
        if (mpsse->mode >= SPI0 && mpsse->mode <= SPI3)
        {
            buf = malloc (size);
            if (buf)
            {
                memset (buf, 0, size);

                while(n < size)
                {
                    /* When sending and recieving, FTDI chips don't seem to like large data blocks. Limit the size of each block to SPI_TRANSFER_SIZE */
                    rxsize = size - n;
                    if (rxsize > SPI_TRANSFER_SIZE)
                    {
                        rxsize = SPI_TRANSFER_SIZE;
                    }

                    txdata = build_block_buffer (mpsse, mpsse->txrx, (unsigned char *) (data + n), rxsize, &data_size);
                    if (txdata)
                    {
                        retval = raw_write (mpsse, txdata, data_size);
                        free (txdata);

                        if (retval == MPSSE_OK)
                        {
                            n += raw_read (mpsse, (buf + n), rxsize);
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }

#ifdef SWIGPYTHON
    swig_string_data sdata = { 0 };
    sdata.size = n;
    sdata.data = (char *) buf;
    return sdata;
#else
    return (char *) buf;
#endif
}

/*
 * Returns the last received ACK bit.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns either an ACK (0) or a NACK (1).
 */
int GetAck (struct mpsse_context *mpsse)
{
    int ack = 0;

    if (is_valid_context (mpsse))
    {
        ack = (mpsse->rack & 0x01);
    }

    return ack;
}

/*
 * Sets the transmitted ACK bit.
 *
 * @mpsse - MPSSE context pointer.
 * @ack   - 0 to send ACKs, 1 to send NACKs.
 *
 * Returns void.
 */
void SetAck (struct mpsse_context *mpsse, int ack)
{
    if (is_valid_context (mpsse))
    {
        if (ack == NACK)
        {
            mpsse->tack = 0xFF;
        }
        else
        {
            mpsse->tack = 0x00;
        }
    }

    return;
}

/*
 * Causes libmpsse to send ACKs after each read byte in I2C mode.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns void.
 */
void SendAcks (struct mpsse_context *mpsse)
{
    return SetAck (mpsse, ACK);
}

/*
 * Causes libmpsse to send NACKs after each read byte in I2C mode.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns void.
 */
void SendNacks (struct mpsse_context *mpsse)
{
    return SetAck (mpsse, NACK);
}

/*
 * Send data stop condition.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int Stop (struct mpsse_context *mpsse)
{
    int retval = MPSSE_OK;

    if (is_valid_context (mpsse))
    {
        /* In I2C mode, we need to ensure that the data line goes low while the clock line is low to avoid sending an inadvertent start condition */
        if (mpsse->mode == I2C)
        {
            retval |= set_bits_low (mpsse, (mpsse->pidle & ~DO & ~SK));
        }

        /* Send the stop condition */
        retval |= set_bits_low (mpsse, mpsse->pstop);

        if (retval == MPSSE_OK)
        {
            /* Restore the pins to their idle states */
            retval |= set_bits_low (mpsse, mpsse->pidle);
        }

        mpsse->status = STOPPED;
    }
    else
    {
        retval = MPSSE_FAIL;
        mpsse->status = STOPPED;
    }

    return retval;
}

/*
 * Sets the specified pin high.
 *
 * @mpsse - MPSSE context pointer.
 * @pin   - Pin number to set high.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int PinHigh (struct mpsse_context *mpsse, int pin)
{
    int retval = MPSSE_FAIL;

    if (is_valid_context (mpsse))
    {
        retval = gpio_write (mpsse, pin, HIGH);
    }

    return retval;
}

/*
 * Sets the specified pin low.
 *
 * @mpsse - MPSSE context pointer.
 * @pin   - Pin number to set low.
 *
 * Returns MPSSE_OK on success.
 * Returns MPSSE_FAIL on failure.
 */
int PinLow (struct mpsse_context *mpsse, int pin)
{
    int retval = MPSSE_FAIL;

    if (is_valid_context (mpsse))
    {
        retval = gpio_write (mpsse, pin, LOW);
    }

    return retval;
}

/*
 * Sets the input/output direction of all pins. For use in BITBANG mode only.
 *
 * @mpsse     - MPSSE context pointer.
 * @direction - Byte indicating input/output direction of each bit.  1 is out.
 *
 * Returns MPSSE_OK if direction could be set, MPSSE_FAIL otherwise.
 */
int SetDirection (struct mpsse_context *mpsse, uint8_t direction)
{
    int retval = MPSSE_FAIL;

    if (is_valid_context (mpsse))
    {
        if (mpsse->mode == BITBANG)
        {
            if (ftdi_set_bitmode (&mpsse->ftdi, direction, BITMODE_BITBANG) == 0)
            {
                retval = MPSSE_OK;
            }
        }
    }

    return retval;
}

/*
 * Sets the input/output value of all pins. For use in BITBANG mode only.
 *
 * @mpsse - MPSSE context pointer.
 * @data  - Byte indicating bit hi/low value of each bit.
 *
 * Returns MPSSE_OK if direction could be set, MPSSE_FAIL otherwise.
 */
int WritePins (struct mpsse_context *mpsse, uint8_t data)
{
    int retval = MPSSE_FAIL;

    if (is_valid_context (mpsse))
    {
        if (mpsse->mode == BITBANG)
        {
            if (ftdi_write_data (&mpsse->ftdi, &data, 1) == 0)
            {
                retval = MPSSE_OK;
            }
        }
    }

    return retval;
}

/*
 * Reads the state of the chip's pins. For use in BITBANG mode only.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns a byte with the corresponding pin's bits set to 1 or 0.
 */
int ReadPins (struct mpsse_context *mpsse)
{
    uint8_t val = 0;

    if (is_valid_context (mpsse))
    {
        ftdi_read_pins ((struct ftdi_context *)&mpsse->ftdi, (unsigned char *)&val);
    }

    return (int) val;
}

/*
 * Checks if a specific pin is high or low. For use in BITBANG mode only.
 *
 * @mpsse - MPSSE context pointer.
 * @pin   - The pin number.
 * @state - The state of the pins, as returned by ReadPins.
 *          If set to -1, ReadPins will automatically be called.
 *
 * Returns a 1 if the pin is high, 0 if the pin is low.
 */
int PinState (struct mpsse_context *mpsse, int pin, int state)
{
    if (state == -1)
    {
        state = ReadPins (mpsse);
    }

    /* If not in bitbang mode, the specified pin should be one of GPIOLx. Convert these defines into an absolute pin number. */
    if (mpsse->mode != BITBANG)
    {
        pin += NUM_GPIOL_PINS;
    }

    return ((state & (1 << pin)) >> pin);
}

/*
 * Places all I/O pins into a tristate mode.
 *
 * @mpsse - MPSSE context pointer.
 *
 * Returns MPSSE_OK on success, MPSSE_FAIL on failure.
 */
int Tristate (struct mpsse_context *mpsse)
{
    unsigned char cmd[CMD_SIZE] = { 0 };

    /* Tristate the all I/O pins (FT232H only) */
    cmd[0] = TRISTATE_IO;
    cmd[1] = 0xFF;
    cmd[2] = 0xFF;

    return raw_write (mpsse, cmd, sizeof (cmd));
}
